package cz.worktracker.controller;

import cz.worktracker.enums.DataSetsEnum;
import cz.worktracker.model.entities.Customer;
import cz.worktracker.model.entities.Taskinterval;
import cz.worktracker.model.entities.Project;
import cz.worktracker.model.entities.Task;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import javax.annotation.ManagedBean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.inject.Inject;
import javax.inject.Named;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Transaction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

/**
 * TaskManager
 * Controller for working with JPA entity Task
 * @since 2010-04-23
 * @author kyjovsky
 */
@ManagedBean
@SessionScoped
@Named
public class TaskManager extends AbstractManager
        implements Serializable {

    private static final long serialVersionUID = 1L;
    private static final String NAV_EDIT = "/user/task/edit";
    private static final String NAV_DETAIL = "/user/task/detail";
    private static final String NAV_REDIRECT = "?faces-redirect=true";
    private Task task;
    private Long timerMsec;
    private Long timerTotalMsec;
    private boolean isTimerStart;
    private Integer timerTask;
    private Integer idActTask;
    private String intervalNote;
    /* TABULKA */
    private AbstractPaginator paginator;
    private DataModel dataModel;
    private DataSetsEnum DSEnum;
    private int rowsPerPage = DSEnum.ITEMS_PER_PAGE_DEFAULT.getValue();
    private String searchPattern = "";
    private String sortByField = DSEnum.SORT_BY_FIELD.getStringValue();
    private String sortDirection = DSEnum.SORT_DIRECTION.getStringValue();
    /* TABULKA */
    
    @Inject UserManager userManager;
    @Inject ProjectManager projectManager;
    @Inject TaskintervalManager taskintervalManager;

    /**
     * Constructor
     */
    @PostConstruct
    public void construct() {
        init();
    }

    /**
     * Destructor
     */
    @PreDestroy
    public void destroy() {
        task = null;
        paginator = null;
        dataModel = null;
    }

    /**
     * Initialize values
     */
    private void init() {
        task = new Task();
        task.setIdProject(-1);
        this.setTimerMsec(0L);
        this.setTimerTask(null);
        this.setIsTimerStart(true);
        this.setTimerTotalMsec(0L);
    }

    /**
     * Reset current task and return true
     * @return true
     */
    public boolean getNewTask() {
        task = null;
        init();
        return true;
    }

    /**
     * This method create new task in db by current task
     * @return String navigation
     */
    public String create() {
        if (task != null) {
            try {
                if( task.getOverheadCost() == null ) {
                    task.setOverheadCost(new Float(0.0));
                }
                task.setIdProject( projectManager.getProject().getId() );
                Transaction tx = getSession().beginTransaction();
                getSession().save( task );
                tx.commit();
                init();
            } catch (Exception e) {
                getLogger(getClass()).log(Level.SEVERE, "Error on try to save Task: " + getTask(), e);
                addMessage("Error on try to save Task", FacesMessage.SEVERITY_ERROR);
                return null;
            }
        }
        return "/user/project/detail" + NAV_REDIRECT;
    }

    /**
     * Method sets current task record for editation.
     * @return String navigation
     */
    public String edit() {
        task = (Task) getList().getRowData();

        return NAV_EDIT + NAV_REDIRECT;
    }


    public void remove() {
        task = (Task) getList().getRowData();

        Transaction tx = getSession().beginTransaction();
        taskintervalManager.removeByTaskId( task.getId() );
        getSession().delete( task );
        tx.commit();

        resetDataModel();
        resetPagination();

        // return NAV_DETAIL + NAV_REDIRECT;
    }


    /**
     * Method removes all tasks according to project id.
     * @param pId
     * @return boolean
     */
    public boolean removeByProjectId( int pId ) {
        Transaction tx = getSession().beginTransaction();
        Criteria ct = getSession().createCriteria(Task.class);
        // ct.add( Projections.property("id") );
        ct.add( Restrictions.eq( "idProject", pId) );

        Iterator it = ct.list().iterator();
        while( it.hasNext() ) {
            Task tmpTask = (Task) it.next();
            taskintervalManager.removeByTaskId( tmpTask.getId() );
            getSession().delete( tmpTask );
        }

        tx.commit();

        return true;
    }


    /**
     * This method update current task in db
     * @return String navigation
     */
    public String update() {
        if(task != null) {
            try {
                if (task.getOverheadCost() == null) {
                    task.setOverheadCost(new Float(0.0));
                }
                Transaction tx = getSession().beginTransaction();
                getSession().update(task);
                tx.commit();
                task = null;
                init();
            } catch (Exception e) {
                getLogger(getClass()).log(Level.SEVERE, "Error on try to save Task: " + getTask(), e);
                addMessage("Error on try to save Task", FacesMessage.SEVERITY_ERROR);
                return null;
            }
        }
        return "/user/project/detail" + NAV_REDIRECT;
    }

    /**
     * Return current task
     * @return Task
     */
    public Task getTask() {
        return task;
    }

    /**
     * Set current task
     * @param task
     */
    public void setTask(Task task) {
        this.task = task;
    }

    /**
     * Prepares dataModel.
     * @return String navigation
     */
    public String prepareDataModel() {
        resetDataModel();
        return NAV_DETAIL + NAV_REDIRECT;
    }

    /**
     * Method nulls reference of dataModel.
     */
    public void resetDataModel() {
        dataModel = null;
    }

    /**
     * Method nulls reference of paginator.
     */
    public void resetPagination() {
        paginator = null;
    }

    /**
     * Returns page switcher and override abstract methods from AbstractPaginator.
     * @return Paginator object.
     */
    public AbstractPaginator getPaginator() {
        if (paginator == null) {
            paginator = new AbstractPaginator(rowsPerPage) {

                /*
                 * While creating a new instance we have to implement abstract
                 * methods defined in AbstractPaginator class.
                 */
                @Override
                public int getItemsCount() {
                    return getProjectTasksCount();
                }

                @Override
                public DataModel createPageDataModel() {
                    ListDataModel list = null;

                    try {
                        Transaction tx = getSession().beginTransaction();
                        Criteria ct = getSession().createCriteria(Task.class);
                        ct.add(Restrictions.eq("idProject", projectManager.getProject().getId()));

                        if (getSearchPattern().length() > 0) {
                            ct.add(Restrictions.like("name", getSearchPattern(), MatchMode.ANYWHERE));
                            ct.add(Restrictions.like("note", getSearchPattern(), MatchMode.ANYWHERE));
                        }

                        if (getSortDirection().equalsIgnoreCase("asc")) {
                            ct.addOrder(Order.asc(getSortByField()));
                        } else {
                            ct.addOrder(Order.desc(getSortByField()));
                        }

                        ct.setFirstResult(getPageFirstItem());
                        ct.setMaxResults(getPageSize());
                        list = new ListDataModel(ct.list());
                        tx.commit();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    return list;
                }
            };
        }
        return paginator;
    }

    /**
     * Returns count of records returned to view.
     * @return int
     */
    public int getRowsPerPage() {
        return rowsPerPage;
    }

    /**
     * Sets count of records returned to view.
     * @param rowsPerPage
     */
    public void setRowsPerPage(int rowsPerPage) {
        this.rowsPerPage = rowsPerPage;

        resetDataModel();
        resetPagination();
    }

    /**
     * Returns DataModel for dataTable.
     * @return DataModel for dataTable.
     */
    public DataModel getList() {
        if (dataModel == null) {
            dataModel = getPaginator().createPageDataModel();
        }

        return dataModel;
    }

    /**
     * Next page.
     */
    public void next() {
        getPaginator().nextPage();
        resetDataModel();
    }

    /**
     * Last page.
     */
    public void last() {
        getPaginator().lastPage();
        resetDataModel();
    }

    /**
     * Previous page.
     */
    public void previous() {
        getPaginator().previousPage();
        resetDataModel();
    }

    /**
     * First page.
     */
    public void first() {
        getPaginator().firstPage();
        resetDataModel();
    }

    /**
     * Increments count of items per page.
     */
    public void more() {
        setRowsPerPage(getRowsPerPage() + DSEnum.ITEMS_PER_PAGE_INCREMENT.getValue());

        resetPagination();
        resetDataModel();
    }

    /**
     * * Decrements count of items per page.
     */
    public void less() {
        setRowsPerPage(getRowsPerPage() - DSEnum.ITEMS_PER_PAGE_INCREMENT.getValue());

        resetPagination();
        resetDataModel();
    }

    /**
     * Retrieves sort params through context and initiates sort.
     */
    public void sort() {
        /**
         * Try to gain params send by <f:param />
         */
        FacesContext context = FacesContext.getCurrentInstance();
        Map requestMap = context.getExternalContext().getRequestParameterMap();
        setSortByField((String) requestMap.get("by"));
        setSortDirection((String) requestMap.get("order"));

        resetPagination();
        resetDataModel();

    }

    /**
     * Returns tasks of current project count.
     * @return int means tasks count.
     */
    public int getProjectTasksCount() {
        Transaction tx = getSession().beginTransaction();
        String hql = "select count(*) from Task as task where task.idProject = '" + projectManager.getProject().getId() + "'";
        int count = ((Long) (getSession().createQuery(hql).iterate().next())).intValue();
        tx.commit();
        return count;
    }

    /**
     * Returns field name in JPA entity according which sorting will be provided.
     * @return String
     */
    public String getSortByField() {
        return sortByField;
    }

    /**
     * Sets field name in JPA entity according which sorting will be provided.
     * @param sortByField
     */
    public void setSortByField(String sortByField) {
        this.sortByField = sortByField;
    }

    /**
     * Returns sort direction (ascending: asc; descending desc).
     * @return String
     */
    public String getSortDirection() {
        return sortDirection;
    }

    /**
     * Sets sort direction (ascending: asc; descending desc).
     * @param sortDirection
     */
    public void setSortDirection(String sortDirection) {
        this.sortDirection = sortDirection;
    }

    /**
     * Returns searched string.
     * @return String
     */
    public String getSearchPattern() {
        return searchPattern;
    }

    /**
     * Sets searched string.
     * @param searchPattern
     */
    public void setSearchPattern(String searchPattern) {
        this.searchPattern = searchPattern;
    }

    /**
     * Return timer state of current Task
     * If timer is stopped return false
     * If timer is started return true
     * @return boolean true, if timer is exists, else false
     */
    public boolean getTimer() {
        int id_task = ((Task) getList().getRowData()).getId();

        ListDataModel list = null;

        try {
            Transaction tx = getSession().beginTransaction();
            Criteria ct = getSession().createCriteria(Taskinterval.class);
            ct.add(Restrictions.eq("idTask", id_task));

            list = new ListDataModel(ct.list());
            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }

        setIsTimerStart(false);
        Taskinterval interval;
        long diff = 0;
        setTimerMsec(0L);
        for (int i = 0; i < list.getRowCount(); i++) {
            list.setRowIndex(i);
            interval = (Taskinterval) list.getRowData();
            if (interval.getStopTime() == null) {
                setTimerMsec((new Date()).getTime() - interval.getStartTime().getTime());
                diff += getTimerMsec();
                setIsTimerStart(true);
            } else {
                diff += interval.getStopTime().getTime() - interval.getStartTime().getTime();
                setIsTimerStart(false);
            }

        }

        setTimerTotalMsec(diff);
        setTimerTask(id_task);

        return true;
    }


    /**
     * Set timer as started in db
     */
    public void getStartTimer() {
        Taskinterval newInterval = new Taskinterval();

        task = (Task) getList().getRowData();

        newInterval.setIdTask(task.getId());
        newInterval.setStartTime(new Date());

        try {
            Transaction tx = getSession().beginTransaction();
            getSession().save(newInterval);
            tx.commit();
            init();
        } catch (Exception e) {
            getLogger(getClass()).log(Level.SEVERE, "Error on try to save Interval: " + newInterval, e);
            addMessage("Error on try to save Interval", FacesMessage.SEVERITY_ERROR);
        }

    }

    /**
     * Set actual selected task asi current task
     */
    public void saveRow() {
        idActTask = ((Task) getList().getRowData()).getId();
        intervalNote = "";
    }


    /**
     *  Set timer of actual selected task as stopped
     */
    public void stopTimer() {
        int taskId = idActTask;
        try {
            Transaction tx = getSession().beginTransaction();
            Criteria ct = getSession().createCriteria(Taskinterval.class);
            ct.add(Restrictions.eq("idTask", taskId));
            ct.add(Restrictions.isNull("stopTime"));
            System.out.println("DB:" + ct.list().size());
            Taskinterval newInterval = (Taskinterval) ct.list().get(0);

            newInterval.setStopTime(new Date());
            newInterval.setNote(intervalNote);

            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * Return isTimerStart value.
     * @return boolean isTimerStart
     */
    public boolean isIsTimerStart() {
        return isTimerStart;
    }

    /**
     * Set isTimerStart value
     * @param isTimerStart value
     */
    public void setIsTimerStart(boolean isTimerStart) {
        this.isTimerStart = isTimerStart;
    }

    /**
     * Return getTimerMsec value.
     * @return Long
     */
    public Long getTimerMsec() {
        return timerMsec;
    }

    /**
     * Set timerMsec value
     * @param timerMsec value
     */
    public void setTimerMsec(Long timerMsec) {
        this.timerMsec = timerMsec;
    }

    /**
     * Return getTimerTask value.
     * @return Integer
     */
    public Integer getTimerTask() {
        return timerTask;
    }

    /**
     * Set timerTask value
     * @param timerTask value
     */

    public void setTimerTask(Integer timerTask) {
        this.timerTask = timerTask;
    }

    /**
     * Return getTimerTotalMsec value.
     * @return Long
     */
    public Long getTimerTotalMsec() {
        return timerTotalMsec;
    }

    /**
     * Set timerTotalMsec value
     * @param timerTotalMsec value
     */
    public void setTimerTotalMsec(Long timerTotalMsec) {
        this.timerTotalMsec = timerTotalMsec;
    }

    /**
     * Method sets current task record for show detail.
     * @return String navigation
     */
    public String detail() {

        task = (Task) getList().getRowData();
        resetDataModel();
        return NAV_DETAIL + NAV_REDIRECT;
    }

    /**
     * Based on actual row, method returns Project name
     * @return String, if project not found return null
     */
    public String getProjectName() {

        int idProject = task.getIdProject();

        if (idProject > 0) {
            Project tmpProject = getProjectById(idProject);
            return tmpProject.getName();
        }

        return null;
    }

    /**
     * Method returns Project by his id.
     * @param id Project ID
     * @return Project
     */
    public Project getProjectById(int id) {
        Transaction tx = getSession().beginTransaction();
        String hql = "from Project as project where project.id = '" + id + "'";
        Query query = getSession().createQuery(hql);
        query.setMaxResults(1);
        Project project = (Project) query.uniqueResult();
        tx.commit();
        return project;
    }

    /**
     * Get intervalNote value
     * @return String intervalNote value
     */
    public String getIntervalNote() {
        return intervalNote;
    }

    /**
     * Set intervalNote value
     * @param intervalNote value
     */
    public void setIntervalNote(String intervalNote) {
        this.intervalNote = intervalNote;
    }

    /**
     * Returns task ids of current user.
     * @return ArrayList of Integers
     */
    public ArrayList<Integer> getTaskIds() {

        ArrayList<Integer> projectIds = projectManager.getProjectIds();
        ArrayList<Integer> result = new ArrayList<Integer>();

        try {
            Transaction tx = getSession().beginTransaction();
            Criteria ct = getSession().createCriteria(Task.class);
            ct.add(Restrictions.in("idProject", projectIds.toArray()));
            Iterator it = ct.list().iterator();

            while (it.hasNext()) {
                Task tmpTask = (Task) it.next();
                result.add(tmpTask.getId());
            }
            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }

    /**
     * Returns task by ID
     * @param id identificator
     * @return Task
     */
    public Task getTaskById(int id) {
        Transaction tx = getSession().beginTransaction();
        String hql = "from Task as task where task.id = '" + id + "'";
        Query query = getSession().createQuery(hql);
        query.setMaxResults(1);
        task = (Task) query.uniqueResult();
        tx.commit();
        return task;
    }

    /**
     * Returns true. Always!
     * @return boolean
     */
    public boolean getTrue() {
        return true;
    }
}
